/* Programm für Drehstrom-Phasenfinder II
* für ATtiny13 (auch ATtiny11, ATtiny12? Fehlt PWM beim Timer)
* Hardware:
* Das Gerät hat eine „Antenne“ zum kapazitiven Abgriff der Spannung
* (das Erdpotenzial bildet der Bediener); es misst also das elektrische Feld.
* Damit wird das Mikrocontrollerprogramm aufgeweckt, das dann anschließend
* die Periodendauer und danach die Phasenlage misst.
* Drei LEDs zeigen die Phasenlage in je 64 Zwischenstufen an.
* „h#s“ Henrik Haftmann, TU Chemnitz, 12. Februar 2011
Anzeigen:
Alle LEDs aus Standby-Betrieb
Alle LEDs leuchten Frequenz-Suche
1-2 LEDs leuchten Phasen-Verfolgung und -Anzeige
1-2 LEDs blinken kein Eingangssignal, letzte Phase wird angezeigt
Alle LEDs blinken kein Eingangssignal, dann Übergang zum Standby
*
* tabsize = 8, encoding = utf-8 (ohne Windows-Header BOM)
*/
#define DATEYEAR 2013
#define DATEMONTH 2
#define DATEDAY 1
#define DEBUG
/************
* Hardware *
************/
/*
Verwendung der Ports:
PB0 (5) - LED L1 nach Masse
PB1 (6) - LED L2 nach Masse
PB2 (7) - LED L3 nach Masse
PB3 (2) - Gemeinsame LED und Piepser — oder Debug-Ausgang
PB4 (3) - Antenne mit externem Pull-Down
PB5 (1) RESET frei
*/
#define L1 0 // Bitnummern für LEDs
#define L2 1
#define L3 2
#define AN 4 // Bitnummer für Antenne
#define DO 3 // Bitnummer Piep-Ausgang
#define LDN 5 // Anzahl Perioden zur Frequenzbestimmung (4 => 16)
#define TO 7 // Zeit der Synchronhaltung, in s (TimeOut)
// 0 = Maximum, max. 127
#define TAPO 5 // Zeit bis zum Ausschalten, in s (Auto Power Off)
// 0 = 128, max. 127
// Erwartete Timerticks zwischen zwei fallenden Pegeln
#define F_TMR F_CPU/8 // 150 kHz
// (Ungenauigkeit des Oszillators einkalkulierend)
#define TMAX F_TMR/45 // 3809, entsprechend 45 Hz
#define TMIN F_TMR/65 // 2307, entsprechend 65 Hz
#define TDEV 40 // entsprechend ± 0,? Hz
#define TTOT F_TMR/25 // 6000, nur High-Teil = 23 wird benutzt
#define TGLI F_TMR/1000 // 150, alles unter 1 ms ist ein Glitch
/**********
* Makros *
**********/
.nolist
.include "tn13def.inc"
.macro addHL ;reg,reg
add @0L,@1L
adc @0H,@1H
.endm
.macro subHL ;reg,reg
sub @0L,@1L
sbc @0H,@1H
.endm
.macro cmpHL ;reg,reg
cp @0L,@1L
cpc @0H,@1H
.endm
.macro movHL ;reg,reg
movw @0H:@0L,@1H:@1L
.endm
.macro lsrHL ;reg
lsr @0H
ror @0L
.endm
.macro rorHL ;reg
ror @0H
ror @0L
.endm
.macro clrHL ;reg
clr @0H
clr @0L
.endm
.macro outi ;io,imm (via R16!)
ldi r16,@1
out @0,r16
.endm
.macro subiHL ;reg,imm
subi @0L,LOW(@1)
sbci @0H,HIGH(@1)
.endm
.macro cpiHL ;reg,imm (via R17!)
cpi @0L,LOW(@1)
ldi r17,HIGH(@1)
cpc @0H,r17
.endm
.list
/*********************
* Registerzuordnung *
*********************/
// Dieses Programm benötigt keinen RAM, es sind noch 7 Register frei
.def prevL =r0 // vorhergehender Zeitstempel in Pegelwechsel-ISR
.def prevH =r1
.def periode =r2 // Periodenzähler (exakte Frequenzmessung), 0..16, *
.def t0h =r3 // „High-Teil“ von Timer0, Überlauf alle 0,5 s
.def sregsave =r4 // SREG-Speicher für ISRs mit gesperrten Interrupts
.def riseL =r8 // Timerwert bei steigender Flanke, *
.def riseH =r9
.def L1phaL =r10 // Zeitwert für 0°
.def L1phaH =r11
.def tNL =r12 // Periodendauer für N Perioden sowie Akkumulator
.def tNH =r13
#if LDN>5
.def tNE =r14
.def ZERO =r15
#endif
// frei für ISR: R16 (T0OVL mit freien Interrupts), R17 (übrige Interrupts)
.def tprevL =r18 // vorherige Periodendauer (Frequenz stabil?)
.def tprevH =r19
.def state =r20 // Synchronisierungs-Zustand
// Bit 0 = gültiges Signal (messbare Frequenz) anliegend — kein Blinken,
// dieser Zustand hält TOT*2 ms
// Bit 1 = synchronisiert — dieser Zustand hält TO Sekunden
// und verlängert sich mit vorhandenem plausiblen Signal
// Zustandstabelle für Bits 1:0
// 0 0 Kein Signal Kein Synchrontimer Blinken (3 LEDs)
// 0 1 Signal (neu) Kein Synchrontimer Frequenzmessung
// 1 1 Signal Synchrontimer läuft Phasenmessung
// 1 0 Kein Signal (mehr) Synchrontimer läuft Blinken (letzter Phasenwert)
// Bit 2 = Timer-ISR wollte L1pha inkrementieren (DPC)
// Bit 3 = AN ist HIGH; Timer-ISR darf L1pha nicht inkrementieren
// Bit 4 = DPC-Aufruf (aufgeschobener Prozeduraufruf) zur Phasenberechnung anhängig
// Bit 5 = Timer-Überlauf-ISR in Abarbeitung (Phasenberechnung wird aufgeschoben)
// Bit 6 = frei
// Bit 7 = Blink-Leuchtphase (sonst Pausenphase, kein PWM-Interrupt)
.def phase =r21 // Phasenwinkel, 0..191
.def to =r22 // TimeOut-Zähler (in 2ms oder ½s)
.def ledL =r24 // Leuchtende LED für 0 .. OCR0B
.def ledH =r25 // Leuchtende LED für OCR0B .. 0
.undef XH
.undef XL
.undef YH
.undef YL
.undef ZH
.undef ZL
.def curL =r26 // Zeitstempel oder Periodendauer in Pegelwechsel-ISR
.def curH =r27
.def lastL =r28 // Glitch-Erkennung
.def lastH =r29
// * temporär, auch "missbräuchlich"
.org 0
rjmp startup // * Reset
.dw (((DATEYEAR)-1980)<<9|(DATEMONTH)<5|(DATEDAY)) // INT0
rjmp pinchange // * Pegelwechsel
sbr state,1<<5 // * (5) Timer0-Überlauf (Interrupts umgehend frei …)
sei // (1) EEPROM fertig (… für genaue Zeitmessung …)
inc t0h // (1) Analogvergleicher (… ohne Capture-Register)
rjmp t0ovl // Timer0-CompareA (Software-PWM)
out PORTB,ledH // * Timer0-CompareB
reti // Watchdog-Interrupt
/******************************
* PWM und TimeOuts verwalten *
******************************/
// Diese ISR braucht SREG nicht zu retten, das Hauptprogramm benötigt's nicht.
// Interrupts sind hier freigeschaltet, damit der Pegelwechsel-Interrupt
// unverzüglich den Timer-Wert einfangen kann.
// VR: R16, SREG
t0ovl: sbrc state,7 // Blink-Leuchtphase?
out PORTB,ledL // ja, Phase 0 .. OCR0B
brne toe // t0h == 0?
// Aller 0,5 s: (1) Phase nachführen, (2) Timeouts verwalten, (3) Blinken
sbrc state,1 // Synchronisiert?
rjmp to2 // ja, andere TimeOut-Regeln
sbrc state,0 // Frequenz liegt an?
rjmp toe // ja, kurzes TimeOut auswerten
// 00► TAPO-TimeOut auswerten
dec to
brne tobl
// 00► Stromverbrauch runterfahren und warten auf Pegelwechsel-Interrupt
outi PORTB,0
outi MCUCR,0x70 // PowerDown (keine Takte)
reti
// 1x► (1) Phase nachführen
to2: sbrs state,3 // Erlaubt (AN=Low?) — Bit nur einmal prüfen!
rjmp to2a
sbr state,1<<2 // nein, DPC vermerken
rjmp to2b
to2a: rcall Inc_L1pha
// 1x► (2) TO-Timeout prüfen?
to2b: sbrc state,0 // Frequenz liegt an?
rjmp toe // ja, nur das kurze TimeOut auswerten
// 10► (2) TO-Timeout prüfen!
dec to // InSync-TimeOut?
brne tobl // nein, Synchronisation behalten und weiterblinken
cbr state,1<<1 // keine Synchronisation mehr!
ldi ledL,1<<L1|1<<L2|1<<L3
ldi ledH,1<<L1|1<<L2|1<<L3 // alle 3 LEDs blinken
ldi to,TAPO*2 // Auto-Power-Off-Timeout laden
tobl:
// x0► (3) Blinken: Blinkphase wechseln
subi state,0x80
brmi to5
// Pausenphase beginnt
outi TIMSK0,0x02 // PWM aus ("outi TIFR0,8" kann entfallen!)
outi PORTB,0 // LEDs aus (Piepser ist sowieso schon aus)
rjmp toe
to5: // Leuchtphase beginnt
outi TIMSK0,0x0A // PWM ein (LEDs kommen umgehend)
toe: // Mit jedem Timer-Interrupt (≈ 580 Hz) …
sbrc state,0 // Frequenz anliegend?
rcall chktot // ja, schnelles Time-Out mitzählen
cli
cbr state,1<<5 // (1)
sbrs state,4 // (1)
reti // (4)
rcall phasedpc
cbr state,1<<4
reti
/* Wenn sich <phase> ändert, PWM neu einrichten
╔═══════╦═══════╤═══════╤═══════╗
║phase ║LedL │LedH │OCR0B ║
╟───────╫───────┼───────┼───────╢
║0 ║1 │1 │0 ║
║1..63 ║2 │1 │4..252 ║
║64 ║2 │2 │0 ║
║65..127║3 │2 │4..252 ║
║128 ║3 │3 │0 ║
║129.191║1 │3 │4..252 ║
╚═══════╩═══════╧═══════╧═══════╝
Leuchtintensität # = LED1, * = LED2, LED3 nicht dargestellt:
### *** ### ***
### ###*** *** ### ###***
### ***### *** ### ***###
###********* ############********* ###
-120 0 120 240 360 480°
*/
// VR: R17
PhaseChanged:
// OCR0B mit Phase laden
mov r17,phase // Doppelpufferung ist in Aktion
lsl r17
lsl r17
out OCR0B,r17
ldi ledH,1<<L1|1<<DO
tst phase
breq pc0
ldi ledL,1<<L2|1<<DO
cpi phase,64
brcs pce
ldi ledH,1<<L2|1<<DO
breq pce
ldi ledL,1<<L3|1<<DO
cpi phase,128
brcs pce
ldi ledH,1<<L3|1<<DO
breq pce
pc0: ldi ledL,1<<L1|1<<DO
pce: ret
// x1► TimeOut alle 2 ms prüfen - ggf. zum Blinkbetrieb wechseln
chktot: dec to
brne tote
// x1► keine Frequenz mehr anliegend: blinken lassen
cbr state,1<<0|1<<7 // LED aus (Pausenphase)
cbr ledL,1<<DO // Piepser künftig AUS
cbr ledH,1<<DO
outi TIMSK0,0x02 // Pausenphase, kein PWM mehr
outi PORTB,0 // Piepser und LEDs sofort AUS
ldi to,TO*2
sbrs state,1 // noch synchronisiert?
ldi to,TAPO*2
tote: ret
/*****************************
* Frequenz und Phase messen *
*****************************/
MulDiv:
// PE: cur = Dividend (vzl.)
// rise = Divisor (vzl.), zwingend > Dividend
// PA: periode = Quotient (0..191) = Dividend*192/Divisor
// cur = Rest
// VR: periode, tprev, rise, R17
l0: clr periode
clr r17
movHL tprev,cur // cur:periode = 24-bit-Dividend (hier 256 × Dividend)
lsrHL tprev
ror r17
lsrHL tprev
ror r17 // tmp:R17 = 64 × Dividend
sub periode,r17
sbc curL,tprevL
sbc curH,tprevH // cur:periode = 192 × Dividend
ldi r17,8 // Rundenzähler
l1: lsl periode
rol curL
rol curH
brcs l2
cmpHL cur,rise
brcs l3
l2: subHL cur,rise
inc periode
l3: dec r17
brne l1
ret
CalcPhase:
// PE: cur = Zeitstempel der letzten Impulsmitte (nach N Perioden)
// PA:
// VR: cur, rise, periode, R17
subHL cur,L1pha // Zeitdifferenz
brcs cp1
cp0: subHL cur,tN // auf ganze N Perioden normalisieren
brcc cp0 // max. 2 Runden
cp1: addHL cur,tN // bei Unterlauf Periodendauer addieren (positiv machen)
movHL rise,tN
ldi r17,4
cp4: lsrHL rise // LDN Wiederholungen des Schiebens
dec r17
brne cp4 // rise = Periodendauer _einer_ Periode
adc riseL,r17
adc riseH,r17 // runden
cp2: subHL cur,rise // auf ganze Perioden normalisieren
brcc cp2 // max. 16 Runden
addHL cur,rise // nunmehr 0 ≤ cur < rise
rcall MulDiv
mov phase,periode
rjmp PhaseChanged
// ISR für Pegelwechsel (auch asynchron zum Aufwecken möglich)
// Interrupts sind gesperrt
// VR: R17..R19 (neben Arbeitsregistern)
pinchange:
// Zuallererst 16-Bit-TickCount lesen, Interrupts müssen gesperrt sein
// curH:curL = Timer-Wert (überlauf-sicher)
// ► Capture nachbilden
in curL,TCNT0 // sofort Timerwert holen
mov curH,t0h
in r17,TIFR0
sbrc r17,1
ldi curL,0xFF // könnte übergelaufen sein (auf 0), zurückrunden
// ► Tiefschlaf beenden — Oszillator muss Timer antreiben
ldi r17,0x20
out MCUCR,r17 // zurück zum Schlafmodus mit Timer
// ► Glitch erkennen, nichts tun bei Glitch
subHL last,cur // last = -Zeitdifferenz
cpiHL last,-TGLI
brcc pex // Zeitdifferenz zu kurz!
movHL last,cur
sbis PINB,AN // Steigende Flanke?
rjmp ch1 // nein
// ► Steigende Flanke abspeichern
sbr state,1<<3
movHL rise,cur // Nur Timerwert aufnehmen
pex: reti
ch1: in sregsave,SREG
cbr state,1<<3
// ► Bei fallender Flanke H-Pulsmitte berechnen
cmpHL cur,rise // im Regelfall C=0, bei 0-Überschneidung C=1
in r17,SREG // C-Flag (Bit 0) retten
addHL cur,rise
rorHL cur // 17-bit-Zwischenrergebnis benutzen, >>1
sbrc r17,0 // C (vom Vergleich) war gesetzt?
subi curH,0x80 // MSB kippen!
// ► Periodendauer ermitteln
subHL cur,prev // Zeitdifferenz zwischen 2 Pulsmitten
addHL prev,cur // jetzigen Zeitstempel ablegen
// ► Periodendauer grob prüfen, raus bei Fehler (plötzlicher Phasensprung)
cpiHL cur,TMAX
brcc che // T zu groß, f < 45 Hz
cpiHL cur,TMIN
brcs che // T zu klein, f > 65 Hz
// ► Gehe in Zustand „Frequenz anliegend“
sbrc state,0
rjmp af
sbr state,1<<0|1<<7 // beende (langsames) Blinken
// x1► Bereite Periodendauermessung vor
sbrc state,1 // War synchronisiert?
rjmp as
clr periode // Frequenzmessung initialisieren
clrHL tN
// x1► Piepser aktivieren
as: sbr ledL,1<<DO
sbr ledH,1<<DO
outi TIMSK0,0x0A // PWM ein (falls nicht bereits gesetzt)
// x1► Wenn nicht synchronisiert, Periodendauer bestimmen
af: sbrc state,1
rjmp at
// 01► Periodendauer-Schwankung prüfen
tst periode // Schon mal Periodendauer gemessen?
breq ch3 // nein, nur Vergleichswert abspeichern
subHL tprev,cur // Differenz zum vorherigen Messwert
subiHL tprev,-TDEV
subiHL tprev,2*TDEV
brcs ch3 // T schwankt - Abweichung klein genug
// 01► Periodendauer-Messung neu starten
clr periode
clrHL tN
rjmp che
ch3: movHL tprev,cur // zum nächsten Vergleich abspeichern
// 01► gültige Periodendauermessung, akkumulieren auf N Perioden (0,3 s)
addHL tN,cur // Periodendauer aufsummieren (max. 36912 .. 60944)
#if LDN>5
adc tNE,ZERO
#endif
inc periode
// 01► nach N Perioden Frequenz- und Phasenauswertung
sbrs periode,LDN
rjmp che
// x1► Phasenmessung initialisieren (x=0) oder Phase ausspucken (x=1)
at: sbrs state,5
rcall phasedpc // SetupPhase() oder ShowPhase()
sbrc state,5
sbr state,1<<4
// xx► Schnelles TimeOut zur Detektierung des Pulsausfalls scharf machen
che: sbrc state,0
ldi to,HIGH(TTOT) // nur bei Zustand "x1"
sbrc state,2
rcall Inc_L1pha
out SREG,sregsave
reti
Inc_L1pha:
to22: addHL L1pha,tN // addieren bis zum Überlauf
brcc to22 // Es bleibt 0 ≤ L1pha < tN
cbr state,1<<2
ret
// (Möglicherweise) verzögerter, aufgeschobener Prozeduraufruf
// Interrupts müssen ausgeschaltet sein!
// PE: tNa = akkumulierte Periodendauer (N = 16 Perioden)
// prev = Zeitstempel der letzten Impulsmitte
// PA: tN = Periodendauer für N (16) Perioden
// L1pha = Zeitpunkt für 0°-Phase
// phase = Phasenwert, 0..191
// tNa = periode = 0
// VR: R17
phasedpc:
movHL cur,prev // cur = Zeitstempel
sbrs state,1 // schon mal synchronisiert?
rjmp SetupPhase
// 11► ShowPhase
sbrc curH,7
rjmp CalcPhase
sbrc state,2
rcall Inc_L1pha // Bei kleinem Zeitstempel L1pha für die neue Periode setzen
rjmp CalcPhase
// 01► SetupPhase: 0° annehmen
SetupPhase:
sbr state,1<<1 // Synchronisierung setzen
#if LDN == 5
sec
rorHL tN // halbieren und Bit 15 setzen
#endif
#if LDN>5
ldi phase,LDN-4
sp: lsr tNE
rorHL tN
dec phase
brne sp
#endif
ch5: subHL cur,tN // auf kleinstmöglichen Wert normalisieren
brcc ch5 // maximal 2 Runden
addHL cur,tN // nun 0 <= cur < tN
movHL L1pha,cur // abspeichern
clr phase // 0° liefern
rjmp PhaseChanged // LEDs aktualisieren
/*************************************
* Initialisierung und Hauptschleife *
*************************************/
startup:
outi SPL,RAMEND
sbi ACSR,ACD // Analogvergleicher ausschalten
outi TCCR0A,0x03 // Schneller PWM-Modus ohne Portpins
outi TCCR0B,0x02 // Vorteiler 8; Überlauffrequenz ca. 500 Hz
outi DDRB,1<<L1|1<<L2|1<<L3|1<<DO // LEDs als Ausgang
outi PCMSK,1<<AN
outi TIMSK0,0x0A // 2 Timer0-Interrupts aktivieren
outi MCUCR,0x20 // Sleep (Idle-Modus) aktivieren
// ldi r16,0x20
out GIMSK,r16 // Pegelwechsel-Interrupt aktivieren
ldi state,0x80
ldi to,TAPO*2
ldi ledL,1<<L1|1<<L2|1<<L3
ldi ledH,1<<L1|1<<L2|1<<L3 // alle 3 LEDs blinken
#if LDN>5
clr ZERO
#endif
sei
m1: sleep
rjmp m1
Detected encoding: UTF-8 | 0
|